<?php
namespace voxfa\net\store\pdo;

use voxfa\net\Inet4Address;
use voxfa\net\Inet6Address;
use voxfa\net\InetAddress;
use voxfa\net\store\InetAddressProvider;
use voxfa\net\store\pdo\InetAddressConstants;

class InetAddressStore implements InetAddressProvider,InetAddressConstants {
	
	protected $prefix;
	protected $con;
	
	public function __construct($dsn,$user = null,$pass = null,$options = array(),$prefix="voxfa_"){
		$this->con = new PDO($dsn,$user,$pass,$options);
		$this->prefix = $prefix;
	}
	
	public function selectByName($name){
		$sql = sprintf(self::SELECT_BY_NAME,$this->prefix.self::TABLE);
		$stmt = $this->con->prepare($sql);
		$stmt->bindValue(":name",$name);
		$stmt->execute();
		$results = $stmt->fetch(PDO::FETCH_ASSOC);
		$v = array();
		foreach($results as $result){
			$v[]=$this->hydrateObject($result);
		}
		return $v;
	}
	/**
	 * @return net\InetAddress
	 * @param string $address
	 */
	public function selectByAddress($address){
		$address = $this->formatterAddress($address);
		$sql = sprintf(self::SELECT_BY_ADDRESS,$this->prefix.self::TABLE);
		$stmt = $this->con->prepare($sql);
		$stmt->bindValue(":address",$address);
		$stmt->execute();
		$result = $stmt->fetch(PDO::FETCH_ASSOC);
		return $this->hydrateObject($result);
	}
	
	public function deleteByName($interfaceName){
		$sql = sprintf(self::DELETE_BY_NAME,$this->prefix.self::TABLE);
		$stmt=$this->con->prepare($sql);
		$stmt->bindValue(":name",$interfaceName);
		return $stmt->execute();
	}
	
	public function deleteByAddress($address){
		$address = $this->formatterAddress($address);
		$sql = sprintf(self::DELETE_BY_ADDRESS,$this->prefix.self::TABLE);
		$stmt=$this->con->prepare($sql);
		$stmt->bindValue(":address",$address);
		return $stmt->execute();		
	}
	
	public function existAddress($address){
		$address = $this->formatterAddress($address);
		$sql = sprintf(self::EXIST_BY_ADDRESS,$this->prefix.self::TABLE);
		$stmt=$this->con->prepare($sql);
		$stmt->bindValue(":address",$address);
		$stmt->execute();
		return $stmt->fetchColumn(0)>0;
	}
	
	public function insertAddress(InetAddress $address){
		$sql = sprintf(self::INSERT_ADDRESS,$this->prefix.self::TABLE);
		$stmt=$this->con->prepare($sql);
		$stmt->bindValue(":name",$address->getName());
		$stmt->bindValue(":address",$address->getAddress());
		$stmt->bindValue(":attr",$this->formatterAttributes($address->getAttributes()));
		return $stmt->execute();
	}
	
	public function updateAddress(InetAddress $address){
		$ip = $this->formatterAddress($address->getAddress());
		$sql = sprintf(self::UPDATE_ADDRESS,$this->prefix.self::TABLE);
		$stmt=$this->con->prepare($sql);
		$stmt->bindValue(":address",$ip);
		$stmt->bindValue(":attr",$this->formatterAttributes($address->getAttributes()));
		return $stmt->execute();
	}
	
	public function hydrateObject($result){
		$address = $this->parseAddress($result['address']);
		if ($this->isIpv4($address)) {
			$new = new Inet4Address();
		} else if($this->isIpv6($address)) {
			$new = new Inet6Address();
		}else {
			throw new InvalidArgumentException("invalid address");
		}
		$new->setName($result['dev']);
		$new->setAddress($address);
		$new->setAttributes($this->parseAttributes($result['attributes']));
		return $new;
	}
	
	private function formatterAttributes($params){
		$v = array();
		foreach($params as $key=>$value){
			$v[]=$key.":".$value;
		}
		return implode(";",$v);
	}
	
	private function parseAttributes($paramString){
		$params = array();
		$v = explode(";",$paramString);
		foreach($v as $unit){
			list($key,$value)=explode(":",$unit);
			$params[$key]=$value;
		}
		return $params;
	}
	
    private function formatterIpv4Address($address){
		$maskNum = array_pop($address);
		$ip = implode(".",$address);
		
		return $ip."/".$maskNum;
	}
	
	private function formatterIpv6Address($address){
		$maskNum = array_pop($address);
		$ip = implode(":",$address);
		
		return $ip."/".$maskNum;
	}
	
   	private function formatterAddress($address){
		if ($this->isIpv4($address)) {
			return $this->formatterIpv4Address($address);
		} else if($this->isIpv6($address)) {
			return $this->formatterIpv6Address($address);
		} else {
			throw new InvalidArgumentException("invalid address");
		}
   	}
   	
   	private function parseAddress($addressString){
   		if($this->isIpv4($this->parseIpv4Address($addressString))==true) {
   			return $this->parseIpv4Address($addressString);
   		} else if ($this->isIpv6($this->parseIpv6Address($addressString))==true) {
   			return $this->parseIpv6Address($addressString);
   		}
   	}
	
	private function parseIpv4Address($addressString){
		list($addr,$mask)= explode("/");
		
		$ret = explode(".",$addr);
		$ret[]=$mask;
		
		return $ret;
	}
	
	private function parseIpv6Address($addressString){
		list($addr,$mask)= explode("/");
		
		$ret = explode(":",$addr);
		$ret[]=$mask;
		
		return $ret;
	}
	
    private function isIpv6($address){
		$mask = array_pop($address);
		return count($address)==8&&(0<=$mask&&$mask<=128);
	}
	
	private function isIpv4($address){
		$mask = array_pop($address);
		return count($address)==4&&(0<=$mask&&$mask<=32);
	}
}